package main

import (
	"bytes"
	"database/sql"
	"encoding/binary"
	"errors"
	bin "mangos/internal/binary"
	"mangos/internal/crypto/hash"
	"mangos/internal/crypto/srp6"
	"mangos/internal/math/big"
	"mangos/mangos/util"
)

// commandCode 命令码
type commandCode byte

const (
	cmdAuthLogonChallenge commandCode = 0x00 // 请求登录
	cmdAuthLogonProof     commandCode = 0x01 // 请求验证
	cmdAuthRecChallenge   commandCode = 0x02 // 请求重连登录
	cmdAuthRecProof       commandCode = 0x03 // 请求重连验证
	cmdRealmList          commandCode = 0x10 // 请求服务器列表
)

// errorCode 错误码
type errorCode byte

const (
	wowSuccess            errorCode = 0x00 // 成功
	wowFailUnknownAccount errorCode = 0x04 // 未知账号
)

// commandHandler 命令处理程序
type commandHandler func(c *conn, buf []byte) int64

// commandHandlers 命令处理程序表
var commandHandlers map[commandCode]commandHandler

// srp6SSize SRP6算法中随机数 s 的长度
const srp6SSize = 32

// pktAuthLogonChallenge 登录请求数据包
type pktAuthLogonChallenge struct {
	cmd          byte   // 命令码
	error        byte   // 错误码
	size         uint16 // 数据长度（不包含包头）
	gameName     string // 游戏名字
	version1     byte   // 版本号1
	version2     byte   // 版本号2
	version3     byte   // 版本号3
	build        uint16 // 构建版本
	platform     string // 平台
	os           string // 操作系统
	country      string // 国家
	timezoneBias uint32 // 时区
	ip           uint32 // IP地址
	iLen         byte   // i字段的数据长度
	i            []byte // i（用户名）
}

// pktAuthLogonProof 登录验证数据
type pktAuthLogonProof struct {
	cmd     byte   // 命令
	A       []byte // SRP6
	M1      []byte // SRP6
	crcHash []byte // CRC
	nKeys   byte
	secFlag byte
}

// pktAuthRecProof 重连验证数据
type pktAuthRecProof struct {
	cmd   byte   // 命令
	r1    []byte // SRP6
	r2    []byte // SRP6
	r3    []byte // SRP6
	nKeys byte
}

func init() {
	commandHandlers = map[commandCode]commandHandler{
		cmdAuthLogonChallenge: handleLogonChallenge,
		cmdAuthLogonProof:     handleLogonProof,
		cmdAuthRecChallenge:   handleRecChallenge,
		cmdAuthRecProof:       handleRecProof,
		cmdRealmList:          handleRealmList,
	}
	srp6.CalcK = calcKFromS
}

// handleLogonChallenge 处理登录请求
func handleLogonChallenge(c *conn, req []byte) int64 {
	// 接收并解析命令包
	pkt, n := parsePktAuthLogonChallenge(req)
	if n == 0 {
		// 继续等待数据
		return 0
	}
	// 记录数据包大小
	pktSize := n

	log("接收到命令(请求登录): %s, %v", c.RemoteAddr(), pkt)

	_ = c.sendByte(byte(cmdAuthLogonChallenge))
	_ = c.sendByte(0)

	// 获取用户名
	c.login = string(pkt.i)

	var (
		id uint32
		rI string
		v  string
		s  string
	)
	err := rDB.QueryRow(
		"SELECT sha_pass_hash,id,v,s FROM account WHERE username = ?",
		c.login).Scan(&rI, &id, &v, &s)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			// 账号没有找到
			_ = c.sendByte(byte(wowFailUnknownAccount))
			return pktSize
		} else {
			// 数据库错误
			logDBError("%v", err)
			return -1
		}
	}

	log("使用数据库验证信息：u='%s' v='%s' s='%s'", c.login, v, s)

	// 初始化SRP6
	if len(v) != srp6SSize*2 || len(s) != srp6SSize*2 {
		c.srp.Init(c.login, rI, "", "", logDebug)
	} else {
		c.srp.Init(c.login, rI, v, s, logDebug)
	}

	{
		// 获取SRP6信息
		N, g, s, B := c.srp.GetNgsB()
		// 检查公钥
		if B.Len() > 32 {
			return -1
		}

		// 发送错误码
		_ = c.sendByte(byte(wowSuccess))
		// 发送服务端公钥
		_, _ = c.send(B.Binary(32))
		// 发送生成器
		_ = c.sendByte(1)
		_, _ = c.send(g.Binary(0))
		// 发送安全大素数
		_ = c.sendByte(32)
		_, _ = c.send(N.Binary(32))
		// 发送随机数
		_, _ = c.send(s.Binary(srp6SSize))
		// 发送未知大数字
		_, _ = c.send(big.NewRand(16).Binary(16))
		// 发送安全标志
		_ = c.sendByte(0)
	}

	log("账号 '%s' 正使用 '%s'", c.login, pkt.country)
	return pktSize
}

// handleLogonProof 处理验证请求
func handleLogonProof(c *conn, req []byte) int64 {
	// 接收并解析命令包
	pkt, n := parsePktAuthLogonProof(req)
	if n == 0 {
		// 继续等待数据
		return 0
	}
	// 记录数据包大小
	pktSize := n

	log("接收到命令(请求验证): %s, %v", c.RemoteAddr(), pkt)

	// 继续SRP6计算
	A := big.NewBinary(pkt.A)
	err, ok, M := c.srp.Verify(pkt.M1, A)
	if err != nil {
		logError("SRP6计算发生错误: %s", err)
		return -1
	}
	if ok {
		log("账号 '%s' SRP6校验通过", c.login)
		_, err := rDB.Exec("UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), failed_logins = 0 WHERE username = ?",
			c.srp.K.Text(16), c.ip(), c.login)
		if err != nil {
			logDBError("%v", err)
			return -1
		}

		// 计算验证数据并发送
		sha1 := hash.Sha1Hash{}
		sha1.Init()
		sha1.Update(A.Binary(0))
		sha1.Update(M)
		sha1.Update(c.srp.K.Binary(0))
		sendProof(c, sha1.Finalize())
	} else {
		log("账号 '%s' 尝试使用错误的密码登录！", c.login)
		_, _ = c.send([]byte{byte(cmdAuthLogonProof), byte(wowFailUnknownAccount), 3, 0})
	}

	return pktSize
}

// handleRecChallenge 处理重连登录请求
func handleRecChallenge(c *conn, req []byte) int64 {
	// 接收并解析命令包
	pkt, n := parsePktAuthLogonChallenge(req)
	if n == 0 {
		// 继续等待数据
		return 0
	}
	// 记录数据包大小
	pktSize := n

	log("接收到命令(请求重连登录): %s, %v", c.RemoteAddr(), pkt)

	c.login = string(pkt.i)

	// 查询会话秘钥
	var sessionKey string
	err := rDB.QueryRow("SELECT sessionkey FROM account WHERE username = ?", c.login).Scan(&sessionKey)
	if err != nil {
		if !errors.Is(err, sql.ErrNoRows) {
			logDBError("%v", err)
		} else {
			log("在数据库中找不到玩家: %s", c.login)
		}
		return -1
	}
	c.srp.K = big.NewString(sessionKey, 16)

	// 生成一个随机验证数
	c.recProof = big.NewRand(16)

	// 发送响应
	_ = c.sendByte(byte(cmdAuthRecChallenge))
	_ = c.sendByte(0)
	_, _ = c.send(c.recProof.Binary(16))
	_, _ = c.send(bin.IntToBytes(0, 8))
	_, _ = c.send(bin.IntToBytes(0, 8))

	return pktSize
}

// handleRecProof 处理重连验证请求
func handleRecProof(c *conn, req []byte) int64 {
	// 接收并解析命令包
	pkt, n := parsePktAuthRecProof(req)
	if n == 0 {
		// 继续等待数据
		return 0
	}
	// 记录数据包大小
	pktSize := n

	log("接收到命令(请求重连验证): %s, %v", c.RemoteAddr(), pkt)

	// 验证
	t1 := big.NewBinary(pkt.r1)

	// H(I, t1, reProof, K)
	sha1 := &hash.Sha1Hash{}
	sha1.Init()
	sha1.Update([]byte(c.login))
	sha1.Update(t1.Binary(0))
	sha1.Update(c.recProof.Binary(0))
	sha1.Update(c.srp.K.Binary(0))

	if bytes.Compare(sha1.Finalize(), pkt.r2) == 0 {
		log("账号 '%s' 重连SRP6校验通过", c.login)

		_ = c.sendByte(byte(cmdAuthRecProof))
		_ = c.sendByte(0)
		return pktSize
	} else {
		log("账号 '%s' 重连SRP6校验失败！", c.login)
		return -1
	}
}

// handleRealmList 处理服务器列表请求
func handleRealmList(c *conn, req []byte) int64 {
	// 检查请求数据是否接收完成
	if len(req) < 5 {
		return 0
	}
	log("接收到命令(请求服务器列表): %s", c.RemoteAddr())

	// 包大小
	pktSize := 5

	// 查询账号编号
	var accountId uint32 = 0
	err := rDB.QueryRow("SELECT id FROM account WHERE username = ?", c.login).Scan(&accountId)
	if err != nil {
		if !errors.Is(err, sql.ErrNoRows) {
			logDBError("%v", err)
		} else {
			log("在数据库中找不到玩家: %s", c.login)
		}
		return -1
	}

	// 更新服务器列表
	realms.updateIfNeed()

	// 加载服务器列表
	pktLoad := &bytes.Buffer{}
	loadRealmList(pktLoad, accountId)

	// 发送响应
	_ = c.sendByte(byte(cmdRealmList))
	_, _ = c.send(bin.IntToBytes(int64(pktLoad.Len()), 2))
	_, _ = c.send(pktLoad.Bytes())

	return int64(pktSize)
}

// loadRealmList 加载服务器列表
func loadRealmList(buf *bytes.Buffer, accountId uint32) {
	// 获取服务器列表
	rs := realms.getRealms()
	// 服务器数量
	nRs := len(rs)

	// 6141
	buf.Write([]byte{0, 0, 0, 0})
	buf.WriteByte(byte(nRs))
	for _, rm := range rs {
		// 查询角色数量
		nChar := queryCharNumOfRealm(accountId, rm.id)

		// 服务器图标
		buf.Write(bin.IntToBytes(int64(rm.icon), 4))
		// 服务器标志
		buf.WriteByte(byte(rm.realmFlags))
		// 服务器名字
		buf.WriteString(rm.name + " (6141)")
		buf.WriteByte(0)
		// 服务器地址
		buf.WriteString(rm.address)
		buf.WriteByte(0)
		// 服务器热度
		buf.Write(bin.Float32ToBytes(rm.populationLv))
		// 角色数量
		buf.WriteByte(byte(nChar))
		// 时区
		buf.WriteByte(rm.timeZone)
		// 未知
		buf.WriteByte(0)
	}

	// 未知
	buf.Write(bin.IntToBytes(0x0002, 2))
}

// queryCharNumOfRealm 查询账号在指定服务器的角色数量
func queryCharNumOfRealm(accountId uint32, realmId uint32) uint32 {
	var numChars uint32 = 0
	err := rDB.QueryRow("SELECT numChars FROM realmcharacters WHERE realmid = ? AND acctid=?",
		realmId, accountId).Scan(&numChars)
	if err != nil {
		if !errors.Is(err, sql.ErrNoRows) {
			logDBError("%v", err)
		}
	}
	return numChars
}

// calcKFromS 从 S 计算 K
func calcKFromS(S *big.Int) []byte {
	sha1 := hash.Sha1Hash{}

	t := make([]byte, 32)
	t1 := make([]byte, 16)
	vK := make([]byte, 40)

	copy(t, S.Binary(32))
	for i := 0; i < 16; i++ {
		t1[i] = t[i*2]
	}
	sha1.Init()
	sha1.Update(t1)
	digest := sha1.Finalize()
	for i := 0; i < 20; i++ {
		vK[i*2] = digest[i]
	}

	for i := 0; i < 16; i++ {
		t1[i] = t[i*2+1]
	}
	sha1.Init()
	sha1.Update(t1)
	digest = sha1.Finalize()
	for i := 0; i < 20; i++ {
		vK[i*2+1] = digest[i]
	}

	return vK
}

// sendProof 发送验证数据
func sendProof(c *conn, digest []byte) {
	// 6141
	_ = c.sendByte(byte(cmdAuthLogonProof))
	_ = c.sendByte(0)
	_, _ = c.send(digest)
	_, _ = c.send([]byte{0, 0, 0, 0})
}

// parsePktAuthLogonChallenge 解析登录数据包
func parsePktAuthLogonChallenge(b []byte) (*pktAuthLogonChallenge, int64) {
	// 检查包头是否接收完毕
	if len(b) < 4 {
		// 数据包未接受完成，继续接收数据
		return nil, 0
	}

	// 检查包体是否接收完毕
	util.EndianConvert(b[2:4])
	size := binary.LittleEndian.Uint16(b[2:4])
	pktSize := size + 4
	if uint16(len(b)) < pktSize {
		// 数据包未接收完成，继续接收数据
		return nil, 0
	}

	// 解析数据
	pkt := &pktAuthLogonChallenge{}
	pkt.cmd = b[0]
	pkt.error = b[1]
	pkt.size = binary.LittleEndian.Uint16(b[2:4])
	util.EndianConvert(b[4:8])
	pkt.gameName = string(b[4:8])
	pkt.version1 = b[8]
	pkt.version2 = b[9]
	pkt.version3 = b[10]
	util.EndianConvert(b[11:13])
	pkt.build = binary.LittleEndian.Uint16(b[11:13])
	util.EndianConvert(b[13:17])
	pkt.platform = string(b[13:17])
	util.EndianConvert(b[17:21])
	pkt.os = string(b[17:21])
	util.EndianConvert(b[21:25])
	bin.Flip(b[21:25])
	pkt.country = string(b[21:25])
	util.EndianConvert(b[25:29])
	pkt.timezoneBias = binary.LittleEndian.Uint32(b[25:29])
	util.EndianConvert(b[29:33])
	pkt.ip = binary.LittleEndian.Uint32(b[29:33])
	pkt.iLen = b[33]
	pkt.i = b[34:]

	return pkt, int64(pktSize)
}

// parsePktAuthLogonProof 解析登录验证数据包
func parsePktAuthLogonProof(b []byte) (*pktAuthLogonProof, int64) {
	// 检查数据是否接收完成
	if len(b) < 75 {
		return nil, 0
	}

	// 解析数据
	pkt := &pktAuthLogonProof{}
	pkt.cmd = b[0]
	pkt.A = b[1:33]
	pkt.M1 = b[33:53]
	pkt.crcHash = b[53:73]
	pkt.nKeys = b[73]
	pkt.secFlag = b[74]

	return pkt, 75
}

// parsePktAuthRecProof 解析连验证数据包
func parsePktAuthRecProof(b []byte) (*pktAuthRecProof, int64) {
	// 检查数据是否接收完成
	if len(b) < 58 {
		return nil, 0
	}

	// 解析数据
	pkt := &pktAuthRecProof{}
	pkt.cmd = b[0]
	pkt.r1 = b[1:17]
	pkt.r2 = b[17:37]
	pkt.r3 = b[37:57]
	pkt.nKeys = b[57]

	return pkt, 58
}
